package com.ydj.framework.third.qywx;

import com.google.gson.Gson;
import com.ydj.AppEnvironment;
import com.ydj.framework.exception.CommonException;
import com.ydj.framework.exception.VerifyTokenException;
import com.ydj.framework.filter.ExtHttpServletRequest;
import com.ydj.framework.interceptor.InterceptorContext;
import com.ydj.framework.protocol.JsonResult;
import com.ydj.framework.third.qywx.protocol.req.AlarmRobotMarkdownMsgReq;
import com.ydj.framework.third.qywx.protocol.req.AlarmRobotTextMsgReq;
import com.ydj.framework.third.qywx.protocol.res.AlarmRobotMsgRes;
import com.ydj.framework.utils.DateUtils;
import com.ydj.framework.utils.IpUtils;
import com.ydj.framework.utils.JsonUtils;
import com.ydj.framework.utils.OkHttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Date;
import java.util.List;

import static com.ydj.framework.third.qywx.AlarmRobot.COLOR.GREEN;
import static com.ydj.framework.third.qywx.AlarmRobot.COLOR.ORANGE;
import static com.ydj.framework.third.qywx.AlarmRobot.FONT_BOLD.FONT_BOLD_HEAVY;
import static com.ydj.framework.third.qywx.AlarmRobot.FONT_BOLD.FONT_BOLD_NORMAL;
import static com.ydj.framework.third.qywx.AlarmRobot.FONT_SIZE.FONT_SIZE_BIG;
import static com.ydj.framework.third.qywx.AlarmRobot.FONT_SIZE.FONT_SIZE_NORMAL;
import static com.ydj.framework.third.qywx.AlarmRobot.TITLE_STYLE.HEADER;
import static com.ydj.framework.third.qywx.AlarmRobot.TITLE_STYLE.NOT_HEADER;

/**
 * 企业微信报警系统（消息发送频率限制：每个机器人发送的消息不能超过20条/分钟）
 *
 * @author Max
 */
@Slf4j
@Aspect
@Component
@Order(0)
public class AlarmRobot {
	/**
	 * 字体颜色
	 */
	@Retention(RetentionPolicy.SOURCE)
	public @interface COLOR {
		String GREEN = "'info'";
		String ORANGE = "'warning'";
	}

	/**
	 * 字体大小
	 */
	@Retention(RetentionPolicy.SOURCE)
	public @interface FONT_SIZE {
		int FONT_SIZE_BIG = 1;
		int FONT_SIZE_NORMAL = 2;
	}

	/**
	 * 字体粗细
	 */
	@Retention(RetentionPolicy.SOURCE)
	public @interface FONT_BOLD {
		int FONT_BOLD_HEAVY = 1;
		int FONT_BOLD_NORMAL = 2;
	}

	/**
	 * 内容是否为头标题
	 */
	@Retention(RetentionPolicy.SOURCE)
	public @interface TITLE_STYLE {
		int HEADER = 1;
		int NOT_HEADER = 2;
	}

	@Autowired
	private Environment environment;

	@Autowired
	private AppEnvironment appEnvironment;

	/**
	 * 项目名
	 */
	private static final String PROJECT_NAME = "app";

	private static String BASE_ROBOT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=";

	/**
	 * 机器人的 URL
	 */
	private static String ROBOT_URL;

	@PostConstruct
	private void construct() {
		String key = environment.getProperty("robot.key");
		ROBOT_URL = BASE_ROBOT_URL + key;
		if (appEnvironment.isProd() || appEnvironment.isTest()) {
			Assert.notNull(key, "请在 YML 文件中配置企业微信机器人的 KEY: robot.key");
		}
	}

	/**
	 * 报警
	 *
	 * @param joinPoint     切入点
	 * @param returnData    切入方法的返回值
	 */
	@AfterReturning(pointcut = "execution(* *..GlobalExceptionHandler.*(..))", returning = "returnData")
	private void alarm(JoinPoint joinPoint, JsonResult returnData) throws IOException {
		Exception exception = (Exception) joinPoint.getArgs()[0];
		// 这些异常不报警
		if (exception instanceof NoHandlerFoundException || exception instanceof VerifyTokenException || exception instanceof HttpRequestMethodNotSupportedException) {
			return;
		}
		// 有一些不需要报警的错误
		if (exception instanceof CommonException) {
			if (!((CommonException) exception).isReportToWorkWechat()) {
				return;
			}
		}
		sendAlarmMsg(exception, returnData.getErrMsg());
	}

	/**
	 * 发送报警信息到企业微信
	 *
	 * @param errMsg    错误信息
	 */
	private void sendAlarmMsg(Exception exception, String errMsg) throws IOException {
		ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if (attribute == null) {
			log.error("sendAlarmMsg's attribute is null");
			return;
		}

		HttpServletRequest request = attribute.getRequest();
		HttpServletResponse response = attribute.getResponse();
		if (response == null) {
			log.error("sendAlarmMsg's response is null");
			return;
		}

		// 获取报警内容信息
		String status = String.valueOf(response.getStatus());
		String api = request.getRequestURI();
		String ip = IpUtils.getIpAddr(request);
		String token = request.getHeader("Authorization");
		String userAgent = request.getHeader("user-agent");
		String requestParameters = null;
		String requestId = null;
		String id = null;
		String name = null;

		if (request instanceof ExtHttpServletRequest) {
			// 获取 token 里记录的信息
			ExtHttpServletRequest extRequest = (ExtHttpServletRequest) request;
			InterceptorContext context = extRequest.getInterceptorContext();
			if (context != null && context.getRdsLoginInfo() != null) {
				id = context.getRdsLoginInfo().getIdForLog();
				name = context.getRdsLoginInfo().getNameForLog();
			}

			String httpMethod = request.getMethod();
			if ("POST".equalsIgnoreCase(httpMethod)) {
				requestParameters = extRequest.getRequestStringForLog();
			} else if ("GET".equalsIgnoreCase(httpMethod)) {
				requestParameters = extRequest.getQueryString();
			}
			requestId = String.valueOf(extRequest.getRequestId());
		}

		// 获取异常位置
		StackTraceElement[] stackTrace = exception.getStackTrace();
		StringBuilder stackInfo = new StringBuilder();
		boolean hasPrintYdjTrace = false;

		for (StackTraceElement trace : stackTrace) {
			String traceStr = trace.toString();
			boolean startWithYdj = trace.getClassName().startsWith("com.ydj");
			if (!hasPrintYdjTrace && !startWithYdj) {
				continue;
			} else if (hasPrintYdjTrace && !startWithYdj) {
				break;
			} else {
				hasPrintYdjTrace = true;
			}
			stackInfo.append(traceStr).append("\n");
		}

		// 拼接 content 字段的内容，字体加粗后字体颜色设置会失效，| 的样式和字体变大不能同时使用
		StringBuilder content = new StringBuilder();
		content.append(createRow("项目", PROJECT_NAME, HEADER, ORANGE, FONT_SIZE_BIG, FONT_BOLD_HEAVY))
				.append(createRow("环境", appEnvironment.getBuildType()))
				.append(createRow("时间", DateUtils.dateToString(new Date())))
				.append(createRow("IP", ip))
				.append(createRow("请求 ID", requestId))
				.append(createRow("接口", api))
				.append(createRow("请求参数", requestParameters))
				.append(createRow("响应状态码", status))
				.append(createRow("Token", token))
				.append(createRow("ID", id))
				.append(createRow("名字", name))
				.append(createRow("客户端型号", userAgent))
				.append(createRow("异常信息", exception.getClass().getName(), NOT_HEADER, ORANGE, FONT_SIZE_NORMAL, FONT_BOLD_HEAVY))
				.append(createRow("异常位置", stackInfo.toString()))
				.append(createRow("错误信息", errMsg, NOT_HEADER, ORANGE, FONT_SIZE_NORMAL, FONT_BOLD_HEAVY));

		AlarmRobotMarkdownMsgReq.MarkdownBean markdownBean = new AlarmRobotMarkdownMsgReq.MarkdownBean()
																.setContent(StringUtils.truncate(content.toString(), 4096));
		AlarmRobotMarkdownMsgReq req = new AlarmRobotMarkdownMsgReq()
											.setMarkdown(markdownBean);
		String qywxResponse = OkHttpClientUtils.postJson(ROBOT_URL, new Gson().toJson(req));
		AlarmRobotMsgRes res = JsonUtils.jsonToBean(qywxResponse, AlarmRobotMsgRes.class);

		if (res == null || !res.getErrcode().equals(0)) {
			String resErrMsg = "";
			if (res != null) {
				resErrMsg = res.getErrmsg();
			}
			log.error("发生报警信息到企业微信机器人异常 {}", resErrMsg);
		}
	}

	/**
	 * 创建一行报警信息
	 *
	 * @param title         报警内容的标题
	 * @param info          报警内容
	 * @param headerStyle   标题样式
	 * @param color         字体颜色
	 * @param fontSize      字体大小
	 * @param fontBold      字体粗细
	 * @return              报警信息
	 */
	private StringBuilder createRow(String title, String info, @TITLE_STYLE int headerStyle, @COLOR String color, @FONT_SIZE int fontSize, @FONT_BOLD int fontBold) {
		StringBuilder content = new StringBuilder();
		if (fontSize == FONT_SIZE_BIG) {
			content.append("#### ");
		}

		if (headerStyle != HEADER) {
			content.append("> ");
		}

		content.append(title).append("：").append("<font color=").append(color).append(">");

		if (fontBold == FONT_BOLD_HEAVY) {
			content.append("**");
		}

		if (StringUtils.isBlank(info)) {
			content.append("无");
		} else {
			content.append(info);
		}

		if (fontBold == FONT_BOLD_HEAVY) {
			content.append("**");
		}

		content.append("</font>\n");
		return content;
	}

	/**
	 * 创建一行默认格式的报警信息（非标题、绿色、正常大小粗细）
	 *
	 * @param title 报警内容的标题
	 * @param info  报警内容
	 * @return      报警信息
	 */
	private StringBuilder createRow(String title, String info) {
		return createRow(title, info, NOT_HEADER, GREEN, FONT_SIZE_NORMAL, FONT_BOLD_NORMAL);
	}


	public boolean sendText(AlarmRobotTextMsgReq req, List<String> keyList) {
		for (String key : keyList) {
			if (StringUtils.isBlank(key)) {
				continue;
			}
			String qywxResponse = OkHttpClientUtils.postJson(BASE_ROBOT_URL + key, new Gson().toJson(req));
			AlarmRobotMsgRes res = JsonUtils.jsonToBean(qywxResponse, AlarmRobotMsgRes.class);

			if (res == null || !res.getErrcode().equals(0)) {
				String resErrMsg = "";
				if (res != null) {
					resErrMsg = res.getErrmsg();
				}
				log.error("发送信息到企业微信机器人异常 {}", resErrMsg);
				return false;
			}
		}
		return true;
	}

}